﻿using System;
using Exceptions;

namespace RSA.SignatureProviders
{
	/// <summary>
	/// All custom signature providers must implement this interface.  The 
	/// RSACrypto class handles encryption and decryption of data.  The 
	/// SignatureProvider is intended to provide the hashing and signature
	/// generation mechanism used to create the comparison data.
	/// </summary>
	public interface ISignatureProvider
	{
		/// <summary>
		/// Generates a hash for the input data.
		/// </summary>
		/// <param name="dataBytes">Data to be signed</param>
		/// <param name="params">RSA Parameters used for signature calculation</param>
		/// <returns>Computed signature (pre-encryption)</returns>
		byte[] EncodeSignature(byte[] dataBytes, RSA.RSAParameters @params);
		/// <summary>
		/// Verifies the signed data against the unsigned data after decryption.
		/// </summary>
		/// <param name="dataBytes">Unsigned data</param>
		/// <param name="signedDataBytes">Signed data (after decryption)</param>
		/// <param name="params">RSAParameters used for signature computation</param>
		/// <returns>Boolean representing whether the input data matches the signed data</returns>
        bool VerifySignature(byte[] dataBytes, byte[] signedDataBytes, RSA.RSAParameters @params);
	}
}

namespace RSA.SignatureProviders
{
	/// <summary>
	/// Uses the DER (Distinguished Encoding Rules) 
	/// and the SHA1 hash provider for encoding generation.
	/// </summary>
	public sealed class EMSAPKCS1v1_5_SHA1 : SignatureProviders.ISignatureProvider
	{

		//Default hash provider for hashing operations.
		private HashProviders.IHashProvider m_hashProvider;

		//Length of the hash generated by the hash provider
		private int m_hLen;

		/// <summary>
		/// Default constructor
		/// </summary>
		public EMSAPKCS1v1_5_SHA1()
		{
			m_hashProvider = new HashProviders.SHA1();
			m_hLen = 20;
		}

		/// <summary>
		/// Hashes and encodes the signature for encryption.  Uses the DER (Distinguished Encoding Rules) 
		/// and the SHA1 hash provider for encoding generation.
		/// </summary>
		/// <param name="dataBytes">Data to be signed</param>
		/// <param name="params">RSA Parameters used for signature calculation</param>
		/// <returns>Computed signature (pre-encryption)</returns>
        public byte[] EncodeSignature(byte[] dataBytes, RSA.RSAParameters @params)
		{
			//Set the intended message length (key length)
			int emLen = @params.N.Length;

			//Compute the hash of the data
			byte[] H = m_hashProvider.ComputeHash(dataBytes);
			//Get the digest encoding information for the hash being used.
			byte[] bytDigestEncoding = Utils.DigestEncoding.SHA1();

			//Create the hashed message including the digest info
			byte[] T = new byte[(bytDigestEncoding.Length + m_hLen)];
			bytDigestEncoding.CopyTo(T, 0);
			H.CopyTo(T, bytDigestEncoding.Length);

			H = null;
			bytDigestEncoding = null;

			if (emLen < T.Length + 11)
			{
				throw new CryptographicException("Message too short.");
			}

			//Create the padding string, octet string of 0xff
			byte[] PS = new byte[(emLen - T.Length - 3)];
			for (int i = 0; i <= PS.Length - 1; i++)
			{
				PS[i] = 0xff;
			}

			byte[] result = new byte[emLen];

			//Add the leading identifier bytes
			result[0] = 0x00;
			result[1] = 0x01;

			//Copy the padding string to the result
			PS.CopyTo(result, 2);

			//Add the separator byte
			result[PS.Length + 2] = 0x00;

			//Copy the digest info
			T.CopyTo(result, PS.Length + 3);
			PS = null;
			T = null;

			return result;
		}

		/// <summary>
		/// Verifies the signed data against the unsigned data after decryption.
		/// </summary>
		/// <param name="dataBytes">Unsigned data</param>
		/// <param name="signedDataBytes">Signed data (after decryption)</param>
		/// <param name="params">RSAParameters used for signature computation</param>
		/// <returns>Boolean representing whether the input data matches the signed data</returns>
		bool ISignatureProvider.VerifySignature(byte[] dataBytes, byte[] signedDataBytes, RSA.RSAParameters @params)
		{
			byte[] EM2 = EncodeSignature(dataBytes, @params);

			if (!(EM2.Length == signedDataBytes.Length))
			{
				return false;
			}

			bool isValid = true;

			for (int i = 0; i <= EM2.Length - 1; i++)
			{
				if (!(EM2[i] == signedDataBytes[i]))
				{
					isValid = false;
					break;
				}
			}

			return isValid;
		}
	}

	/// <summary>
	/// Uses the DER (Distinguished Encoding Rules) 
	/// and the SHA256 hash provider for encoding generation.
	/// </summary>
	public sealed class EMSAPKCS1v1_5_SHA256 : SignatureProviders.ISignatureProvider
	{

		//Default hash provider for hashing operations.
		private HashProviders.IHashProvider m_hashProvider;

		//Length of the hash generated by the hash provider
		private int m_hLen;

		/// <summary>
		/// Default constructor
		/// </summary>
		public EMSAPKCS1v1_5_SHA256()
		{
			m_hashProvider = new HashProviders.SHA256();
			m_hLen = 32;
		}

		/// <summary>
		/// Hashes and encodes the signature for encryption.  Uses the DER (Distinguished Encoding Rules) 
		/// and the SHA256 hash provider for encoding generation.
		/// </summary>
		/// <param name="dataBytes">Data to be signed</param>
		/// <param name="params">RSA Parameters used for signature calculation</param>
		/// <returns>Computed signature (pre-encryption)</returns>
		public byte[] EncodeSignature(byte[] dataBytes, RSA.RSAParameters @params)
		{
			//Set the intended message length (key length)
			int emLen = @params.N.Length;

			//Compute the hash of the data
			byte[] H = m_hashProvider.ComputeHash(dataBytes);
			//Get the digest encoding information for the hash being used.
			byte[] bytDigestEncoding = Utils.DigestEncoding.SHA256();

			//Create the hashed message including the digest info
			byte[] T = new byte[(bytDigestEncoding.Length + m_hLen)];
			bytDigestEncoding.CopyTo(T, 0);
			H.CopyTo(T, bytDigestEncoding.Length);

			H = null;
			bytDigestEncoding = null;

			if (emLen < T.Length + 11)
			{
				throw new CryptographicException("Message too short.");
			}

			//Create the padding string, octet string of 0xff
			byte[] PS = new byte[(emLen - T.Length - 3)];
			for (int i = 0; i <= PS.Length - 1; i++)
			{
				PS[i] = 0xff;
			}

			byte[] result = new byte[emLen];

			//Add the leading identifier bytes
			result[0] = 0x00;
			result[1] = 0x01;

			//Copy the padding string to the result
			PS.CopyTo(result, 2);

			//Add the separator byte
			result[PS.Length + 2] = 0x00;

			//Copy the digest info
			T.CopyTo(result, PS.Length + 3);
			PS = null;
			T = null;

			return result;
		}

		/// <summary>
		/// Verifies the signed data against the unsigned data after decryption.
		/// </summary>
		/// <param name="dataBytes">Unsigned data</param>
		/// <param name="signedDataBytes">Signed data (after decryption)</param>
		/// <param name="params">RSAParameters used for signature computation</param>
		/// <returns>Boolean representing whether the input data matches the signed data</returns>
		bool ISignatureProvider.VerifySignature(byte[] dataBytes, byte[] signedDataBytes, RSA.RSAParameters @params)
		{
			byte[] EM2 = EncodeSignature(dataBytes, @params);

			if (!(EM2.Length == signedDataBytes.Length))
			{
				return false;
			}

			bool isValid = true;

			for (int i = 0; i <= EM2.Length - 1; i++)
			{
				if (!(EM2[i] == signedDataBytes[i]))
				{
					isValid = false;
					break;
				}
			}

			return isValid;
		}
	}
}